package cn.stevenyang;

import cn.stevenyang.exceptions.VariableNotFoundException;
import cn.stevenyang.exceptions.VariablePathTooDeepException;
import cn.stevenyang.gen.BasicParserBaseVisitor;
import cn.stevenyang.gen.BasicParserParser;
import cn.stevenyang.services.*;
import cn.stevenyang.signals.CounterSignal;
import cn.stevenyang.signals.LinearSignal;
import cn.stevenyang.signals.Signal;
import org.apache.velocity.tools.generic.MathTool;

import java.util.ArrayList;
import java.util.List;

public class BasicInterpreter extends BasicParserBaseVisitor<Object> {

    public NumberService getNumberService() {
        return numberService;
    }
    public ArithmeticService getArithmeticService() {
        return arithmeticService;
    }
    public LogicService getLogicService() {
        return logicService;
    }
    public CompareService getCompareService() {
        return compareService;
    }
    public CallStackService getCallStackService() {
        return callStackService;
    }
    public StringService getStringService() {
        return stringService;
    }
    public SignalService getSignalService() {
        return signalService;
    }
    private final NumberService numberService = new NumberService();
    private final ArithmeticService arithmeticService = new ArithmeticService();
    private final CallStackService callStackService = new CallStackService();
    private final LogicService logicService = new LogicService();
    private final CompareService compareService = new CompareService();
    private final StringService stringService = new StringService();
    private final SignalService signalService = new SignalService();

    @Override
    public Object visitBoolTrue(BasicParserParser.BoolTrueContext ctx) {
        return true;
    }

    @Override
    public Object visitBoolFalse(BasicParserParser.BoolFalseContext ctx) {
        return false;
    }

    @Override
    public Object visitArthmeticAdd(BasicParserParser.ArthmeticAddContext ctx) {
        return arithmeticService.add(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitArthmeticMinus(BasicParserParser.ArthmeticMinusContext ctx) {
        return arithmeticService.minus(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitArthmeticMultiply(BasicParserParser.ArthmeticMultiplyContext ctx) {
        return arithmeticService.multiply(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitArthmeticDivide(BasicParserParser.ArthmeticDivideContext ctx) {
        return arithmeticService.divide(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitArthmeticMod(BasicParserParser.ArthmeticModContext ctx) {
        return arithmeticService.mod(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitNumber(BasicParserParser.NumberContext ctx) {
        return numberService.get(ctx.t.getText());
    }

    @Override
    public Object visitVarDefine(BasicParserParser.VarDefineContext ctx) {
        try {
            callStackService.setVariable(ctx.ID().getText(), visit(ctx.expression()));
        } catch (VariablePathTooDeepException e) {
            throw new RuntimeException(e);
        }
        return true;
    }

    @Override
    public Object visitVarDefineExpr(BasicParserParser.VarDefineExprContext ctx) {
        return visit(ctx.assignment());
    }

    @Override
    public Object visitGenericExpression(BasicParserParser.GenericExpressionContext ctx) {
        /*
        Object result = visit(ctx.expression(0));
        var nextNode = ctx.expression(1);
        if (nextNode != null) {
            return visit(nextNode);
        } else {
            return result;
        }
         */
        Object result = visit(ctx.expression());
        var nextNode = ctx.process();
        if (nextNode != null) {
            return visit(nextNode);
        } else {
            return result;
        }
    }

    @Override
    public Object visitLogicAnd(BasicParserParser.LogicAndContext ctx) {
        return logicService.and(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitLogicOr(BasicParserParser.LogicOrContext ctx) {
        return logicService.or(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitLogicNot(BasicParserParser.LogicNotContext ctx) {
        return logicService.not(visit(ctx.expression()));
    }

    @Override
    public Object visitLogicBitAnd(BasicParserParser.LogicBitAndContext ctx) {
        return logicService.bitAnd(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitLogicBitOr(BasicParserParser.LogicBitOrContext ctx) {
        return logicService.bitOr(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitLogicXor(BasicParserParser.LogicXorContext ctx) {
        return logicService.xor(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitCompareEq(BasicParserParser.CompareEqContext ctx) {
        return compareService.eq(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitCompareGt(BasicParserParser.CompareGtContext ctx) {
        return compareService.gt(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitCompareGte(BasicParserParser.CompareGteContext ctx) {
        return compareService.gte(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitCompareLt(BasicParserParser.CompareLtContext ctx) {
        return compareService.lt(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitCompareLte(BasicParserParser.CompareLteContext ctx) {
        return compareService.lte(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitCompareNeq(BasicParserParser.CompareNeqContext ctx) {
        return compareService.neq(visit(ctx.expression(0)), visit(ctx.expression(1)));
    }

    @Override
    public Object visitPriority(BasicParserParser.PriorityContext ctx) {
        return visit(ctx.expression());
    }

    @Override
    public Object visitStr(BasicParserParser.StrContext ctx) {
        return stringService.trimTheQuotationMarks(ctx.STRING().getText());
    }

    @Override
    public Object visitNum(BasicParserParser.NumContext ctx) {
        return numberService.get(ctx.numeric().getText());
    }

    @Override
    public Object visitIfExpr(BasicParserParser.IfExprContext ctx) {
        Boolean ifConditionResult = (Boolean) visit(ctx.expression());
        Object propagation = null;
        if (ifConditionResult) {
            getCallStackService().pushNewStack();
            propagation = visit(ctx.process());
            getCallStackService().popCallStack();
        } else {
            List<BasicParserParser.ElseifexpressionContext> elseIfExpressionList = ctx.elseifexpression();
            boolean foundValidElseIf = false;
            for (var elseIfCondition : elseIfExpressionList) {
                propagation = visit(elseIfCondition);
                if (propagation != null) {
                    foundValidElseIf = true;
                    break;
                }
            }
            if (!foundValidElseIf) {
                var elseExpr = ctx.elseexpression();
                if (elseExpr != null) {
                    propagation = visit(elseExpr);
                }
            }
        }
        return propagation;
    }

    @Override
    public Object visitConcatenateStrStatement(BasicParserParser.ConcatenateStrStatementContext ctx) {
        return stringService.concatenate(((String) visit(ctx.expression(0))), (String) visit(ctx.expression(1)));
    }

    @Override
    public Object visitIfExpressionExpr(BasicParserParser.IfExpressionExprContext ctx) {
        return visit(ctx.ifexpression());
    }

    @Override
    public Object visitElseIfExpr(BasicParserParser.ElseIfExprContext ctx) {
        Object result = visit(ctx.expression());
        Signal propagation;
        if (result instanceof Boolean && (Boolean) result) {
            getCallStackService().pushNewStack();
            propagation = (Signal) visit(ctx.process());
            getCallStackService().popCallStack();
            if (propagation == null) {
                propagation = new Signal();
            }
        } else {
            propagation = null;
        }
        return propagation;
    }

    @Override
    public Object visitElseExpr(BasicParserParser.ElseExprContext ctx) {
        getCallStackService().pushNewStack();
        Object propagation = visit(ctx.process());
        getCallStackService().popCallStack();
        return propagation;
    }

    @Override
    public Object visitForExpr(BasicParserParser.ForExprContext ctx) {
        String idName = ctx.ID().getText();
        Number from = (Number) visit(ctx.expression(0));
        Number to = (Number) visit(ctx.expression(1));
        boolean downTo = ctx.t.getType() == BasicParserParser.DOWNTO;
        MathTool mathTool = new MathTool();
        Object nextStep = ctx.expression(2);
        Number step;
        if (nextStep != null) {
            step = (Number) visit(ctx.expression(2));
        } else {
            step = 1;
        }
        Object propagation = null;
        if (!downTo) {
            Number i = from;
            getCallStackService().pushNewStack();
            try {
                getCallStackService().setVariable(idName, i);
            } catch (VariablePathTooDeepException e) {
                throw new RuntimeException(e);
            }
            for (; Double.parseDouble(mathTool.sub(i, to).toString()) <= 0.0; i = mathTool.add(i, step)) {
                try {
                    getCallStackService().setVariable(idName, i);
                } catch (VariablePathTooDeepException e) {
                    throw new RuntimeException(e);
                }
                var process = ctx.process();
                if (process != null) {
                    Object signal = visit(process);
                    SignalService.Action action = signalService.checkTicket(signal, "for");
                    if (action != SignalService.Action.DO_NOTHING) {
                        if (action == SignalService.Action.STOP_PROPAGATION) {
                            propagation = null;
                        } else { // CONTINUE_PROPAGATION
                            propagation = signal;
                        }
                        if (((Signal) signal).getName().equals("break")) {
                            break;
                        } else if (((Signal) signal).getName().equals("continue")) {
                        }
                    }
                }
            }
            getCallStackService().popCallStack();
        } else {
            Number i = from;
            getCallStackService().pushNewStack();
            try {
                getCallStackService().setVariable(idName, i);
            } catch (VariablePathTooDeepException e) {
                throw new RuntimeException(e);
            }
            for (; Double.parseDouble(mathTool.sub(i, to).toString()) >= 0.0; i = mathTool.sub(i, step)) {
                try {
                    getCallStackService().setVariable(idName, i);
                } catch (VariablePathTooDeepException e) {
                    throw new RuntimeException(e);
                }
                var process = ctx.process();
                if (process != null) {
                    Object signal = visit(process);
                    SignalService.Action action = signalService.checkTicket(signal, "for");
                    if (action != SignalService.Action.DO_NOTHING) {
                        if (action == SignalService.Action.STOP_PROPAGATION) {
                            propagation = null;
                        } else { // CONTINUE_PROPAGATION
                            propagation = signal;
                        }
                        if (((Signal) signal).getName().equals("break")) {
                            break;
                        } else if (((Signal) signal).getName().equals("continue")) {
                        }
                    }
                }
            }
            getCallStackService().popCallStack();
        }
        return propagation;
    }

    @Override
    public Object visitForExprStatement(BasicParserParser.ForExprStatementContext ctx) {
        // 维持不变
        return super.visitForExprStatement(ctx);
    }

    @Override
    public Object visitForEachExpression(BasicParserParser.ForEachExpressionContext ctx) {
        String idName = ctx.ID().getText();
        // TODO
        return super.visitForEachExpression(ctx);
    }

    @Override
    public Object visitForEachExprStatement(BasicParserParser.ForEachExprStatementContext ctx) {
        // 维持不变
        return super.visitForEachExprStatement(ctx);
    }

    @Override
    public Object visitWhileExpr(BasicParserParser.WhileExprContext ctx) {
        getCallStackService().pushNewStack();
        Object propagation = null;
        while ((Boolean) visit(ctx.expression())) {
            Object signal = visit(ctx.process());
            SignalService.Action action = signalService.checkTicket(signal, "while");
            if (action != SignalService.Action.DO_NOTHING) {
                if (action == SignalService.Action.STOP_PROPAGATION) {
                    propagation = null;
                } else { // CONTINUE_PROPAGATION
                    propagation = signal;
                }
                if (((Signal) signal).getName().equals("break")) {
                    break;
                } else if (((Signal) signal).getName().equals("continue")) {
                }
            }
        }
        getCallStackService().popCallStack();
        return propagation;
    }

    @Override
    public Object visitDoLoopExpr(BasicParserParser.DoLoopExprContext ctx) {
        getCallStackService().pushNewStack();
        Object propagation = null;
        do {
            Object signal = visit(ctx.process());
            SignalService.Action action = signalService.checkTicket(signal, "do-loop");
            if (action != SignalService.Action.DO_NOTHING) {
                if (action == SignalService.Action.STOP_PROPAGATION) {
                    propagation = null;
                } else { // CONTINUE_PROPAGATION
                    propagation = signal;
                }
                if (((Signal) signal).getName().equals("break")) {
                    break;
                } else if (((Signal) signal).getName().equals("continue")) {
                }
            }
        } while ((Boolean) visit(ctx.expression()));
        getCallStackService().popCallStack();
        return propagation;
    }

    @Override
    public Object visitWhileExprStatement(BasicParserParser.WhileExprStatementContext ctx) {
        // 维持不变
        return super.visitWhileExprStatement(ctx);
    }

    @Override
    public Object visitBreakStatement(BasicParserParser.BreakStatementContext ctx) {
        CounterSignal signal;
        int loopLevel;
        if (ctx.INT() != null) {
            loopLevel = Integer.parseInt(ctx.INT().getText());
        } else {
            loopLevel = 1;
        }
        signal = new CounterSignal(loopLevel);
        signal.setName("break");
        signal.setParameter("loop-level", loopLevel);
        return signal;
    }

    @Override
    public Object visitContinueStatement(BasicParserParser.ContinueStatementContext ctx) {
        CounterSignal signal;
        int loopLevel;
        if (ctx.INT() != null) {
            loopLevel = Integer.parseInt(ctx.INT().getText());
        } else {
            loopLevel = 1;
        }
        signal = new CounterSignal(loopLevel);
        signal.setName("continue");
        signal.setParameter("loop-level", loopLevel);
        return signal;
    }

    @Override
    public Object visitReturnStatement(BasicParserParser.ReturnStatementContext ctx) {
        return super.visitReturnStatement(ctx);
    }

    @Override
    public Object visitExitSubStatement(BasicParserParser.ExitSubStatementContext ctx) {
        return super.visitExitSubStatement(ctx);
    }

    @Override
    public Object visitFunctionCall(BasicParserParser.FunctionCallContext ctx) {
        LinearSignal linearSignal = (LinearSignal) visit(ctx.funccallparams());
        // TODO 调用函数
        String funcName = ctx.ID().getText();
        if (funcName.equals("Print")) {
            System.out.println(linearSignal.getList().get(0));
        }
        else if (funcName.equals("CStr")) {
            return String.valueOf(linearSignal.getList().get(0));
        }
        return null;
    }

    @Override
    public Object visitFuncCallParamsExpr(BasicParserParser.FuncCallParamsExprContext ctx) {
        ArrayList<Object> parameterList = new ArrayList<>();
        var parameters = ctx.expression();
        for (var p : parameters) {
            parameterList.add(visit(p));
        }
        LinearSignal linearSignal = new LinearSignal();
        linearSignal.setName("func-call-params");
        linearSignal.setList(parameterList);
        return linearSignal;
    }

    @Override
    public Object visitGetVarExpr(BasicParserParser.GetVarExprContext ctx) {
        try {
            return getCallStackService().getVariable((String)(ctx.ID().getText()));
        } catch (VariableNotFoundException | VariablePathTooDeepException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object visitAssignStatement(BasicParserParser.AssignStatementContext ctx) {
        Object result = visit(ctx.expression());
        try {
            getCallStackService().setVariable((String)(ctx.ID().getText()), result);
        } catch (VariablePathTooDeepException e) {
            throw new RuntimeException(e);
        }
        return result;
    }
}